﻿namespace TuneMultiCheckBox.Utils
{
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using System.Linq.Expressions;
  using TuneMultiCheckBoxPlugins.Utils;
  using Microsoft.Xrm.Sdk;

  public static class EntityHelper
  {
    /// <summary>
    /// Gets the extended value.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="entity">The entity.</param>
    /// <param name="propertyExpression">The property expression.</param>
    /// <param name="preImageEntity">The pre image entity.</param>
    /// <returns>Extended Value</returns>
    public static ExtendedValue<T> GetExtendedValue<T>(this Entity entity, Expression<Func<T>> propertyExpression, Entity preImageEntity = null) 
    {
      var propertyName = propertyExpression.ExtractPropertyName();
      return GetExtendedValue<T>(entity, propertyName, preImageEntity);
    }

    /// <summary>
    /// Gets the extended value.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="entity">The entity.</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="preImageEntity">The pre image entity.</param>
    /// <returns>Extended Value</returns>
    public static ExtendedValue<T> GetExtendedValue<T>(this Entity entity, string propertyName, Entity preImageEntity = null) 
    {
      var attributeName = propertyName.ToLower();
      var result = new ExtendedValue<T>();
    
      if (preImageEntity != null)
      {
        var oldValue = default(T);
        var attributeExists = preImageEntity.Contains(attributeName);
        if (attributeExists)
        {
          oldValue = GetAttributeValue<T>(preImageEntity, attributeName); 
        }

        result.OldValue = oldValue;
      }

      if (entity != null)
      {
        result.IsSpecified = entity.Contains(attributeName);

        var newValue = default(T);
        if (result.IsSpecified)
        {
          newValue = GetAttributeValue<T>(entity, attributeName);
        }
        
        result.NewValue = newValue;
      }

      return result;
    }

    /// <summary>
    /// Gets the attribute value.
    /// </summary>
    /// <typeparam name="T">Type of the attribute</typeparam>
    /// <param name="entity">The entity.</param>
    /// <param name="attributeName">Name of the attribute.</param>
    /// <returns>value of T type</returns>
    private static T GetAttributeValue<T>(Entity entity, string attributeName)
    {
      var defaultValue = default(T);
      var attributeExists = entity.Contains(attributeName);
      if (!attributeExists)
      {
        return defaultValue;
      }

      var type = typeof(T);
      var underlyingType = Nullable.GetUnderlyingType(type);
      
      if (underlyingType != null && underlyingType.IsEnum)
      {
        var value = entity.Attributes[attributeName];
        if (value == null)
        {
          return defaultValue;
        }

        var optionSetValue = (OptionSetValue)value;

        return (T)Enum.ToObject(underlyingType, optionSetValue.Value);
      }

      if (TypeHelper.IsGenericEnumerable(type))
      {
        var value = entity.Attributes[attributeName];
        if (value == null)
        {
          return defaultValue;
        }

        var enumerableType = TypeHelper.GetEnumerableType(type);

        var collection = entity.GetAttributeValue<EntityCollection>(attributeName);

        if (collection == null || collection.Entities == null)
        {
          return defaultValue;
        }

        var entities = collection.Entities;
        var containerType = typeof(List<>).MakeGenericType(enumerableType);
        var containerList = (IList)Activator.CreateInstance(containerType);

        var toEntityMethod = typeof(Entity).GetMethod("ToEntity");
        var toEntityGenericMethod = toEntityMethod.MakeGenericMethod(enumerableType);

        foreach (var e in entities)
        {
          containerList.Add(toEntityGenericMethod.Invoke(e, null));
        }

        return (T)containerList;
      }

      return entity.GetAttributeValue<T>(attributeName);
    }
  }
}
